package com.sunny.ynhz.api.okhttp;

import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.google.gson.Gson;
import com.google.gson.internal.$Gson$Types;
import com.sunny.ynhz.utils.ToastUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.Util;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;

/**
 * Created by hhh on 15/8/17.
 */
public class OkHttpHelper {
    private static OkHttpHelper mInstance;
    private OkHttpClient mOkHttpClient = new OkHttpClient();
    private static Handler mDelivery;
    private Gson mGson;
    private static Context mContext;
    private static String URL;
    private static Map<String, String> map;
    private static View loadView;
    private static ResultCallback callbacks;
    public static TextView tv_load = null;
    private GetDelegate mGetDelegate = new GetDelegate();
    private PostDelegate mPostDelegate = new PostDelegate();
    private UploadDelegate mUploadDelegate = new UploadDelegate();
    private DownloadDelegate mDownloadDelegate = new DownloadDelegate();

    private OkHttpHelper() {
        OkHttpClient.Builder builder = mOkHttpClient.newBuilder();
        builder.connectTimeout(2000, TimeUnit.SECONDS).
                readTimeout(15, TimeUnit.SECONDS).
                writeTimeout(15, TimeUnit.SECONDS).
                build();
        CookieManager cookieManager = new CookieManager();
        cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
        builder.cookieJar(new JavaNetCookieJar(cookieManager));
        builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
        mOkHttpClient = builder.build();

        mDelivery = new Handler(Looper.getMainLooper());
        mGson = new Gson();
    }

    public static OkHttpHelper getInstance() {
        if (mInstance == null) {
            synchronized (OkHttpHelper.class) {
                if (mInstance == null) {
                    mInstance = new OkHttpHelper();
                }
            }
        }
        return mInstance;
    }

    public GetDelegate getGetDelegate() {
        return mGetDelegate;
    }

    public PostDelegate getPostDelegate() {
        return mPostDelegate;
    }

    public static DownloadDelegate getDownloadDelegate() {
        return getInstance()._getDownloadDelegate();
    }

    private DownloadDelegate _getDownloadDelegate() {
        return mDownloadDelegate;
    }

    public static UploadDelegate getUploadDelegate() {
        return getInstance()._getUploadDelegate();
    }

    private UploadDelegate _getUploadDelegate() {
        return mUploadDelegate;
    }


    /**
     * ============对外暴露的Get访问方式============
     */

    public static void getAsyn(Context context, String url, ResultCallback callback) {
        mContext = context;
        getInstance().getGetDelegate().getAsyn(url, callback, null);
    }

    public static void getAsyn(Context context, String url, ResultCallback callback, Object tag) {
        mContext = context;
        getInstance().getGetDelegate().getAsyn(url, callback, tag);
    }

    /**
     * ============对外暴露的POST方访问方式===============
     */
    public static void postAsyn(String url, Param[] params, final ResultCallback callback) {
        getInstance().getPostDelegate().postAsyn(url, params, callback, null);
    }

    public static void postAsyn(View view, String url, Map<String, String> params,
                                final ResultCallback callback) {
//          mContext=context;
//        UserLogin user=(UserLogin) SPUtil.get("user",null);
//        if(user!=null){
//            params.put("token",user.getData().getToken());
//        }
        loadView = view;
        URL = url;
        callbacks = callback;
        map = params;


        getInstance().getPostDelegate().postAsyn(url, params, callback, null);
    }

    public static void postAsyn(String url, String bodyStr, final ResultCallback callback) {
        getInstance().getPostDelegate().postAsyn(url, bodyStr, callback, null);
    }

    public static void postAsyn(String url, Param[] params, final ResultCallback callback,
                                Object tag) {
        getInstance().getPostDelegate().postAsyn(url, params, callback, tag);
    }

    public static void postAsyn(String url, Map<String, String> params,
                                final ResultCallback callback, Object tag) {
        getInstance().getPostDelegate().postAsyn(url, params, callback, tag);
    }

    public static void postAsyn(String url, String bodyStr, final ResultCallback callback,
                                Object tag) {
        getInstance().getPostDelegate().postAsyn(url, bodyStr, callback, tag);
    }

    private Param[] map2Params(Map<String, String> params) {
        if (params == null)
            return new Param[0];
        int size = params.size();
        Param[] res = new Param[size];
        Set<Map.Entry<String, String>> entries = params.entrySet();
        int i = 0;
        for (Map.Entry<String, String> entry : entries) {
            if (entry.getValue() != null) {
                res[i++] = new Param(entry.getKey(), entry.getValue());
            } else {
                res[i++] = new Param(entry.getKey(), "");
            }

        }
        return res;
    }

    private void deliveryResult(ResultCallback callback, final Request request) {
        if (callback == null)
            callback = DEFAULT_RESULT_CALLBACK;
        final ResultCallback resCallBack = callback;
        callback.onBefore(request);
        mOkHttpClient.newCall(request).enqueue(new Callback() {

            private String token;

            @Override
            public void onFailure(Call call, IOException e) {
                sendFailedStringCallback(call.request(), e, resCallBack);
            }

            @Override
            public void onResponse(final Call call, Response response) {
                //---此处需进行判断，响应码有可能是404、500
                //---onResponse有可能只是传送层成功(接收了http响应码，响应头和body部分),不一定代表应用层的成功
                if (response.isSuccessful()) {
                    try {
                        //获取token并保存
                        String string = response.body().string();
                        final JSONObject jsonObject = new JSONObject(string);

                        if (jsonObject.has("code")) {
                            if (jsonObject.getInt("code") == -1) {
                                mDelivery.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        try {
                                            ToastUtils.SingleToastUtil(mContext, jsonObject.getString("msg"));
                                        } catch (JSONException e) {
                                            e.printStackTrace();
                                        }
                                        sendFailedStringCallback(call.request(), null, resCallBack);
                                    }
                                });
                                return;
                            }
                        }


                        if (resCallBack.mType == String.class) {
                            sendSuccessResultCallback(string, resCallBack);
                        } else {
                            Object o = mGson.fromJson(string, resCallBack.mType);
                            //response.body().close();
                            sendSuccessResultCallback(o, resCallBack);
                        }
                    } catch (Exception e) {

                        sendFailedStringCallback(call.request(), e, resCallBack);
                    }
                } else {
                    sendFailedStringCallback(call.request(), new Exception(), resCallBack);
                }
            }
        });
    }

    private void sendFailedStringCallback(final Request request, final Exception e,
                                          final ResultCallback callback) {
        mDelivery.post(new Runnable() {
            @Override
            public void run() {
                callback.onError(request, e);
//                callback.onAfter();
            }
        });
    }


    private void sendSuccessResultCallback(final Object object, final ResultCallback callback) {
        mDelivery.post(new Runnable() {
            @Override
            public void run() {
                callback.onResponse(object);
                callback.onAfter();
            }
        });
    }

    private void sendProgressUpdateCallback(final long bytesRead, final long contentLength, final boolean done, final ResultCallback callback) {
        mDelivery.post(new Runnable() {
            @Override
            public void run() {
                callback.onProgressUpdate(bytesRead, contentLength, done);
            }
        });
    }

    private void sendProgressUpdateCallback(final long bytesRead, final long contentLength, final boolean done, long havDownLen, final ResultCallback callback) {
        mDelivery.post(new Runnable() {
            @Override
            public void run() {
                callback.onProgressUpdate(bytesRead+havDownLen, contentLength+havDownLen, done);
            }
        });
    }


    private Request buildPostFormRequest(String url, Param[] params, Object tag) {
        if (params == null) {
            params = new Param[0];
        }
        FormBody.Builder builder = new FormBody.Builder();
        for (Param param : params) {
            if (param.value != null) {
                builder.add(param.key, param.value);
            } else {
                builder.add(param.key, " ");
            }
        }

        RequestBody requestBody = builder.build();

        Request.Builder reqBuilder = new Request.Builder();
//        String token = SPUtil.get("token", "").toString();
//        if (!token.isEmpty()) {
//            reqBuilder.addHeader("token", token);
//
//        }
        reqBuilder.url(url);
        //新增sessionId
        reqBuilder.post(requestBody);

        if (tag != null) {
            reqBuilder.tag(tag);
        }
        return reqBuilder.build();
    }

    public static void cancelTag(Object tag) {
        getInstance()._cancelTag(tag);
    }

    private void _cancelTag(Object tag) {
        for (Call call : mOkHttpClient.dispatcher().queuedCalls()) {
            if (tag.equals(call.request().tag())) call.cancel();
        }
        for (Call call : mOkHttpClient.dispatcher().runningCalls()) {
            if (tag.equals(call.request().tag())) call.cancel();
        }
    }

    public static OkHttpClient getClinet() {
        return getInstance().client();
    }

    public OkHttpClient client() {
        return mOkHttpClient;
    }

    public static abstract class ResultCallback<T> {
        Type mType;


        public ResultCallback() {
            mType = getSuperclassTypeParameter(getClass());
        }

        static Type getSuperclassTypeParameter(Class<?> subclass) {
            Type superclass = subclass.getGenericSuperclass();
            if (superclass instanceof Class) {
                throw new RuntimeException("Missing type parameter.");
            }
            ParameterizedType parameterized = (ParameterizedType) superclass;
            return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
//            return parameterized.getActualTypeArguments()[0];
        }

        public void onBefore(Request request) {

        }

        public void onAfter() {
            if (loadView != null) {
                loadView.setVisibility(View.GONE);
            }
        }

        public void onError(Request request, Exception e) {
            if (loadView != null) {
                loadView.setVisibility(View.VISIBLE);
                tv_load.setText("加载失败");
                tv_load.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        postAsyn(loadView, URL, map, callbacks);
                    }
                });
            }
        }

        ;

        public abstract void onResponse(T response);

        /**
         * 只有在下载时才有效，
         *
         * @param bytesRead     已经读取的字节
         * @param contentLength 总大小
         * @param done          下载是否完成
         */
        public void onProgressUpdate(long bytesRead, long contentLength, boolean done) {
        }
    }

    private final ResultCallback<String> DEFAULT_RESULT_CALLBACK = new ResultCallback<String>() {
        @Override
        public void onError(Request request, Exception e) {

//            return;

        }

        @Override
        public void onResponse(String response) {

        }
    };

    public static class Param {
        public Param() {
        }

        public Param(String key, String value) {
            this.key = key;
            this.value = value;
        }

        String key;
        String value;
    }

    // ====================PostDelegate=======================
    public class PostDelegate {
        private final MediaType MEDIA_TYPE_STREAM = MediaType
                .parse("application/octet-stream;charset=utf-8");
        private final MediaType MEDIA_TYPE_STRING = MediaType.parse("text/plain;charset=utf-8");
        private Activity activity;

        public Response post(String url, Param[] params) throws IOException {
            return post(url, params, null);
        }

        /**
         * 同步的Post请求
         */
        public Response post(String url, Param[] params, Object tag) throws IOException {
            Request request = buildPostFormRequest(url, params, tag);
            Response response = mOkHttpClient.newCall(request).execute();
            return response;
        }

        public String postAsString(String url, Param[] params) throws IOException {
            return postAsString(url, params, null);
        }

        /**
         * 同步的Post请求
         */
        public String postAsString(String url, Param[] params, Object tag) throws IOException {
            Response response = post(url, params, tag);
            return response.body().string();
        }

        public void postAsyn(Context context, String url, Map<String, String> params, final ResultCallback callback) {

            postAsyn(url, params, callback, null);
        }

        public void postAsyn(String url, Map<String, String> params, final ResultCallback callback,
                             Object tag) {
            Param[] paramsArr = map2Params(params);
            postAsyn(url, paramsArr, callback, tag);
        }

        public void postAsyn(String url, Param[] params, final ResultCallback callback) {
            postAsyn(url, params, callback, null);
        }

        /**
         * 异步的post请求
         */
        public void postAsyn(String url, Param[] params, final ResultCallback callback, Object tag) {
            Request request = buildPostFormRequest(url, params, tag);
            deliveryResult(callback, request);
        }

        /**
         * 同步的Post请求:直接将bodyStr以写入请求体
         */
        public Response post(String url, String bodyStr) throws IOException {
            return post(url, bodyStr, null);
        }

        public Response post(String url, String bodyStr, Object tag) throws IOException {
            RequestBody body = RequestBody.create(MEDIA_TYPE_STRING, bodyStr);
            Request request = buildPostRequest(url, body, tag);
            Response response = mOkHttpClient.newCall(request).execute();
            return response;
        }

        /**
         * 同步的Post请求:直接将bodyFile以写入请求体
         */
        public Response post(String url, File bodyFile) throws IOException {
            return post(url, bodyFile, null);
        }

        public Response post(String url, File bodyFile, Object tag) throws IOException {
            RequestBody body = RequestBody.create(MEDIA_TYPE_STREAM, bodyFile);
            Request request = buildPostRequest(url, body, tag);
            Response response = mOkHttpClient.newCall(request).execute();
            return response;
        }

        /**
         * 同步的Post请求
         */
        public Response post(String url, byte[] bodyBytes) throws IOException {
            return post(url, bodyBytes, null);
        }

        public Response post(String url, byte[] bodyBytes, Object tag) throws IOException {
            RequestBody body = RequestBody.create(MEDIA_TYPE_STREAM, bodyBytes);
            Request request = buildPostRequest(url, body, tag);
            Response response = mOkHttpClient.newCall(request).execute();
            return response;
        }

        /**
         * 直接将bodyStr以写入请求体
         */
        public void postAsyn(String url, String bodyStr, final ResultCallback callback) {
            postAsyn(url, bodyStr, callback, null);
        }

        public void postAsyn(String url, String bodyStr, final ResultCallback callback, Object tag) {
            postAsynWithMediaType(url, bodyStr, MediaType.parse("text/plain;charset=utf-8"),
                    callback, tag);
        }

        /**
         * 直接将bodyBytes以写入请求体
         */
        public void postAsyn(String url, byte[] bodyBytes, final ResultCallback callback) {
            postAsyn(url, bodyBytes, callback, null);
        }

        public void postAsyn(String url, byte[] bodyBytes, final ResultCallback callback, Object tag) {
            postAsynWithMediaType(url, bodyBytes,
                    MediaType.parse("application/octet-stream;charset=utf-8"), callback, tag);
        }

        /**
         * 直接将bodyFile以写入请求体
         */
        public void postAsyn(String url, File bodyFile, final ResultCallback callback) {
            postAsyn(url, bodyFile, callback, null);
        }

        public void postAsyn(String url, File bodyFile, final ResultCallback callback, Object tag) {
            postAsynWithMediaType(url, bodyFile,
                    MediaType.parse("application/octet-stream;charset=utf-8"), callback, tag);
        }

        /**
         * 直接将bodyStr以写入请求体
         */
        public void postAsynWithMediaType(String url, String bodyStr, MediaType type,
                                          final ResultCallback callback, Object tag) {
            RequestBody body = RequestBody.create(type, bodyStr);
            Request request = buildPostRequest(url, body, tag);
            deliveryResult(callback, request);
        }

        /**
         * 直接将bodyBytes以写入请求体
         */
        public void postAsynWithMediaType(String url, byte[] bodyBytes, MediaType type,
                                          final ResultCallback callback, Object tag) {
            RequestBody body = RequestBody.create(type, bodyBytes);
            Request request = buildPostRequest(url, body, tag);
            deliveryResult(callback, request);
        }

        /**
         * 直接将bodyFile以写入请求体
         */
        public void postAsynWithMediaType(String url, File bodyFile, MediaType type,
                                          final ResultCallback callback, Object tag) {
            RequestBody body = RequestBody.create(type, bodyFile);
            Request request = buildPostRequest(url, body, tag);
            deliveryResult(callback, request);
        }

        /**
         * post构造Request的方法
         *
         * @param url
         * @param body
         * @return
         */
        private Request buildPostRequest(String url, RequestBody body, Object tag) {
            Request.Builder builder = new Request.Builder().url(url).post(body);
            if (tag != null) {
                builder.tag(tag);
            }
            Request request = builder.build();
            return request;
        }

    }

    // ====================GetDelegate=======================
    public class GetDelegate {

        private Request buildGetRequest(String url, Object tag) {
            Request.Builder builder = new Request.Builder().url(url);
//            String token = SPUtil.get("token", "").toString();
//            if (!token.isEmpty()) {
//                builder.addHeader("token", token);
//            }
            if (tag != null) {
                builder.tag(tag);
            }
            return builder.build();
        }

        /**
         * 通用的方法
         */
        public Response get(Request request) throws IOException {
            Call call = mOkHttpClient.newCall(request);
            Response execute = call.execute();
            return execute;
        }

        /**
         * 同步的Get请求
         */
        public Response get(String url) throws IOException {
            return get(url, null);
        }

        public Response get(String url, Object tag) throws IOException {
            final Request request = buildGetRequest(url, tag);
            return get(request);
        }

        /**
         * 同步的Get请求
         */
        public String getAsString(String url) throws IOException {
            return getAsString(url, null);
        }

        public String getAsString(String url, Object tag) throws IOException {
            Response execute = get(url, tag);
            return execute.body().string();
        }

        /**
         * 通用的方法
         */
        public void getAsyn(Request request, ResultCallback callback) {
            deliveryResult(callback, request);
        }

        /**
         * 异步的get请求
         */
        public void getAsyn(String url, final ResultCallback callback) {
            getAsyn(url, callback, null);
        }

        public void getAsyn(String url, final ResultCallback callback, Object tag) {
            final Request request = buildGetRequest(url, tag);
            getAsyn(request, callback);
        }
    }

    // ====================UploadDelegate=======================

    /**
     * 上传相关的模块
     */
    public class UploadDelegate {
        /**
         * 同步基于post的文件上传:上传单个文件
         */
        public Response post(String url, String fileKey, File file, Object tag) throws IOException {
            return post(url, new String[]{fileKey}, new File[]{file}, null, tag);
        }

        /**
         * 同步基于post的文件上传:上传多个文件以及携带key-value对：主方法
         */
        public Response post(String url, String[] fileKeys, File[] files, Param[] params, Object tag)
                throws IOException {
            Request request = buildMultipartFormRequest(url, files, fileKeys, params, tag);
            return mOkHttpClient.newCall(request).execute();
        }

        /**
         * 同步单文件上传
         */
        public Response post(String url, String fileKey, File file, Param[] params, Object tag)
                throws IOException {
            return post(url, new String[]{fileKey}, new File[]{file}, params, tag);
        }

        /**
         * 异步基于post的文件上传:主方法
         */
        public void postAsyn(String url, String[] fileKeys, File[] files, Param[] params,
                             ResultCallback callback, Object tag) {
            Request request = buildMultipartFormRequest(url, files, fileKeys, params, tag);
            deliveryResult(callback, request);
        }

        /**
         * 异步基于post的文件上传:单文件不带参数上传
         */
        public void postAsyn(String url, String fileKey, File file, ResultCallback callback,
                             Object tag) throws IOException {
            postAsyn(url, new String[]{fileKey}, new File[]{file}, null, callback, tag);
        }

        /**
         * 异步基于post的文件上传，单文件且携带其他form参数上传
         */
        public void postAsyn(String url, String fileKey, File file, Param[] params,
                             ResultCallback callback, Object tag) {
            postAsyn(url, new String[]{fileKey}, new File[]{file}, params, callback, tag);
        }

        private Request buildMultipartFormRequest(String url, File[] files, String[] fileKeys,
                                                  Param[] params, Object tag) {
            params = validateParam(params);


            MultipartBody.Builder builder = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM);

            for (Param param : params) {
                builder.addPart(
                        Headers.of("Content-Disposition", "form-data; name=\"" + param.key + "\""),
                        RequestBody.create(null, param.value));
            }
            if (files != null) {

                RequestBody fileBody = null;
                for (int i = 0; i < files.length; i++) {
                    File file = files[i];
                    String fileName = file.getName();
                    fileBody = RequestBody.create(MediaType.parse(guessMimeType(fileName)), file);
                    // TODO 根据文件名设置contentType
                    Log.e("hu", fileKeys[0] + "key");
                    builder.addPart(
                            Headers.of("Content-Disposition", "form-data; name=\"" + fileKeys[i]
                                    + "\"; filename=\"" + fileName + "\""), fileBody);


                }
            }

            RequestBody requestBody = builder.build();
            return new Request.Builder().url(url).post(requestBody).tag(tag).build();
        }
    }

    private Param[] validateParam(Param[] params) {
        if (params == null)
            return new Param[0];
        else
            return params;
    }

    private String guessMimeType(String path) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String contentTypeFor = fileNameMap.getContentTypeFor(path);
        if (contentTypeFor == null) {
            contentTypeFor = "application/octet-stream";
        }
        return contentTypeFor;
    }

    // ====================DownloadDelegate=======================

    /**
     * 下载相关的模块
     */
    public class DownloadDelegate {
        /**
         * 异步下载文件
         *
         * @param url
         * @param destFileDir 本地文件存储的文件夹
         * @param callback
         */
        public void downloadAsyn(final String url, final String destFileDir,
                                 final ResultCallback callback, Object tag) {
            final Request request = new Request.Builder().url(url).tag(tag).build();

            mOkHttpClient = mOkHttpClient.newBuilder()
                    .addNetworkInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            Response originalResponse = chain.proceed(chain.request());
                            return originalResponse.newBuilder()
                                    .body(new ProgressResponseBody(originalResponse.body(), callback))
                                    .build();
                        }
                    })
                    .build();
            callback.onBefore(request);
            final Call call = mOkHttpClient.newCall(request);
            File file = getFile(url, destFileDir);
            if (file.exists()) {
                file.delete();
            }
            call.enqueue(new Callback() {

                @Override
                public void onFailure(Call call, IOException e) {
                    sendFailedStringCallback(call.request(), e, callback);
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    InputStream is = null;
                    byte[] buf = new byte[4096];
                    int len = 0;
                    FileOutputStream fos = null;
                    try {
                        is = response.body().byteStream();
                        File file = getFile(url, destFileDir);
                        fos = new FileOutputStream(file);
                        while ((len = is.read(buf)) != -1) {
                            fos.write(buf, 0, len);
                        }
                        fos.flush();
                        // 如果下载文件成功，第一个参数为文件的绝对路径
                        sendSuccessResultCallback(file.getAbsolutePath(), callback);
                    } catch (IOException e) {
                        sendFailedStringCallback(call.request(), e, callback);
                    } finally {
                        try {
                            if (is != null)
                                is.close();
                        } catch (IOException e) {
                        }
                        try {
                            if (fos != null)
                                fos.close();
                        } catch (IOException e) {
                        }
                    }
                }
            });
        }

        /**
         * 异步下载文件
         *
         * @param url
         * @param destFileDir 本地文件存储的文件夹
         * @param callback
         */
        public void downloadBreakConAsyn(final String url, final String destFileDir,
                                         final ResultCallback callback, Object tag) {

            File file = getFile(url, destFileDir);
            long havLoadedlength = 0;

            if (file.exists()) {
                havLoadedlength = file.length();
//                file.delete();
//                return;
            }
//            Log.e("hu",havLoadedlength+"------------>");
            RandomAccessFile savedFile = null;
            try {
                savedFile = new RandomAccessFile(file, "rw");
                try {
                    savedFile.seek(havLoadedlength);//跳过已经下载的字节
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            final Request request = new Request.Builder().url(url).tag(tag).addHeader("RANGE", "bytes=" + havLoadedlength + "-").build();

            long finalHavLoadedlength = havLoadedlength;
            mOkHttpClient = mOkHttpClient.newBuilder().addNetworkInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    Response originalResponse = chain.proceed(chain.request());
                    return originalResponse.newBuilder()
                            .body(new ProgressResponseBody(originalResponse.body(), callback, finalHavLoadedlength))
                            .build();
                }
            }).build();
            callback.onBefore(request);
            final Call call = mOkHttpClient.newCall(request);
            call.enqueue(new Callback() {

                @Override
                public void onFailure(Call call, IOException e) {
                    sendFailedStringCallback(call.request(), e, callback);
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    InputStream is = null;
                    byte[] buf = new byte[4096];
                    int len = 0;
                    FileOutputStream fos = null;
                    try {
                        is = response.body().byteStream();
                        File file = getFile(url, destFileDir);

                        fos = new FileOutputStream(file);
                        while ((len = is.read(buf)) != -1) {
                            fos.write(buf, 0, len);
                        }
                        fos.flush();
                        // 如果下载文件成功，第一个参数为文件的绝对路径
                        sendSuccessResultCallback(file.getAbsolutePath(), callback);
                    } catch (IOException e) {
                        fos.flush();
                        sendFailedStringCallback(call.request(), e, callback);
                    } finally {
                        try {
                            if (is != null)
                                is.close();
                        } catch (IOException e) {
                        }
                        try {
                            if (fos != null)
                                fos.close();
                        } catch (IOException e) {
                        }
                    }
                }
            });
        }

        @NonNull
        private File getFile(String url, String destFileDir) {
            File dir = new File(destFileDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            return new File(dir, getFileName(url));
        }

        private String getFileName(String path) {
            int separatorIndex = path.lastIndexOf("/");
            return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
        }

        public void downloadAsyn(final String url, final String destFileDir,
                                 final ResultCallback callback) {
            downloadAsyn(url, destFileDir, callback, null);
        }
    }

    // 处理进度的自定义响应体
    class ProgressResponseBody extends ResponseBody {

        private final ResponseBody responseBody;
        private ResultCallback callback;
        private BufferedSource bufferedSource;
        private long havDownLenth = 0;

        public ProgressResponseBody(ResponseBody responseBody, ResultCallback callback) {
            this.responseBody = responseBody;
            this.callback = callback;
        }

        public ProgressResponseBody(ResponseBody responseBody, ResultCallback callback, long havDownLenth) {
            this.responseBody = responseBody;
            this.callback = callback;
            this.havDownLenth = havDownLenth;
        }

        @Override
        public MediaType contentType() {
            return responseBody.contentType();
        }

        @Override
        public long contentLength() {
            return responseBody.contentLength();
        }

        @Override
        public BufferedSource source() {

            if (bufferedSource == null) {
                bufferedSource = Okio.buffer(source(responseBody.source()));
            }

            return bufferedSource;
        }

        private Source source(Source source) {

            return new ForwardingSource(source) {

                long totalBytesRead = 0L;

                @Override
                public long read(Buffer sink, long byteCount) throws IOException {
                    long bytesRead = super.read(sink, byteCount);
                    totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                    sendProgressUpdateCallback(totalBytesRead, responseBody.contentLength(), bytesRead == -1, havDownLenth, callback);
//                    callback.onProgressUpdate(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
                    return bytesRead;
                }
            };
        }
    }


    private class JavaNetCookieJar implements CookieJar {
        private final CookieHandler cookieHandler;

        public JavaNetCookieJar(CookieHandler cookieHandler) {
            this.cookieHandler = cookieHandler;
        }

        @Override
        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
            if (cookieHandler != null) {
                List<String> cookieStrings = new ArrayList<>();
                for (Cookie cookie : cookies) {
                    cookieStrings.add(cookie.toString());
                }
                Map<String, List<String>> multimap = Collections.singletonMap("Set-Cookie", cookieStrings);
                try {
                    cookieHandler.put(url.uri(), multimap);
                } catch (IOException e) {
                }
            }
        }

        @Override
        public List<Cookie> loadForRequest(HttpUrl url) {
            // The RI passes all headers. We don't have 'em, so we don't pass 'em!
            Map<String, List<String>> headers = Collections.emptyMap();
            Map<String, List<String>> cookieHeaders;
            try {
                cookieHeaders = cookieHandler.get(url.uri(), headers);
            } catch (IOException e) {
                return Collections.emptyList();
            }

            List<Cookie> cookies = null;
            for (Map.Entry<String, List<String>> entry : cookieHeaders.entrySet()) {
                String key = entry.getKey();
                if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
                        && !entry.getValue().isEmpty()) {
                    for (String header : entry.getValue()) {
                        if (cookies == null) cookies = new ArrayList<>();
                        cookies.addAll(decodeHeaderAsJavaNetCookies(url, header));
                    }
                }
            }

            return cookies != null
                    ? Collections.unmodifiableList(cookies)
                    : Collections.<Cookie>emptyList();
        }

        /**
         * Convert layout_bottom_sheet request header to OkHttp's cookies via {@link -HttpCookie}. That extra step handles
         * multiple cookies in layout_bottom_sheet single request header, which {@link Cookie#parse} doesn't support.
         */
        private List<Cookie> decodeHeaderAsJavaNetCookies(HttpUrl url, String header) {
            List<Cookie> result = new ArrayList<>();
            for (int pos = 0, limit = header.length(), pairEnd; pos < limit; pos = pairEnd + 1) {
                pairEnd = Util.delimiterOffset(header, pos, limit, ";,");
                int equalsSign = Util.delimiterOffset(header, pos, pairEnd, '=');
                String name = Util.trimSubstring(header, pos, equalsSign);
                if (name.startsWith("$")) continue;

                // We have either name=value or just layout_bottom_sheet name.
                String value = equalsSign < pairEnd
                        ? Util.trimSubstring(header, equalsSign + 1, pairEnd)
                        : "";

                // If the value is "quoted", drop the quotes.
                if (value.startsWith("\"") && value.endsWith("\"")) {
                    value = value.substring(1, value.length() - 1);
                }

                result.add(new Cookie.Builder()
                        .name(name)
                        .value(value)
                        .domain(url.host())
                        .build());
            }
            return result;
        }
    }
}
